<?php

namespace yunj\app\admin\service\route;

use think\facade\Db;
use yunj\app\admin\enum\IsDemo;
use yunj\app\admin\enum\route\RouteDataType;
use yunj\app\admin\enum\State;
use yunj\app\admin\enum\IsSystem;
use yunj\app\admin\enum\RedisKey;
use yunj\app\admin\enum\RedisKeyGroup;
use yunj\app\admin\enum\EnableLoginAuth;
use yunj\app\admin\enum\route\RequestMethod;
use yunj\app\admin\model\Model;

/**
 * 路由同步系统数据
 * @package yunj\app\admin\service\route
 */
class RouteSyncSystemDataService extends Service {

    /**
     * 同步系统路由数据
     */
    public static function handle() {
        // 不能随意修改include顺序，有排序处理
        $demoRequests = include YUNJ_VENDOR_SRC_PATH . ds_replace('app/demo/route.php');
        $adminRequests = include YUNJ_VENDOR_SRC_PATH . ds_replace('app/admin/route.php');
        $configTreeRequests = array_merge($demoRequests, $adminRequests, yunj_config('route.', []));
        // 处理配置的树形结构数据，并获取数据库数据
        $dbData = [];
        $parentSystemKeyData = [];
        self::handleTreeItems($dbData, $parentSystemKeyData, '', $configTreeRequests);
        // 处理数据库数据
        self::handleDbData($dbData);
        // 保存数据库数据
        self::saveDbData($dbData, $parentSystemKeyData);

        // 重置路由
        RouteService::reset();
    }

    // 保存数据库数据
    private static function saveDbData(array $dbData, array $parentSystemKeyData) {
        $currTime = self::getHandleTime();
        // 处理增删改数据
        foreach ($dbData as $type => $data) {
            /** @var RouteDataType $typeObj */
            $typeObj = RouteDataType::byValue($type);
            $model = $typeObj->getModel();
            // 删除
            if ($delIds = $data['delIds'] ?? []) {
                $changeData = [
                    'state' => State::DELETED,
                    'last_update_time' => $currTime,
                ];
                if ($tableUniqueKey = $typeObj->getTableUniqueKey()) {
                    foreach ($tableUniqueKey as $uniqueKey) {
                        $changeData[$uniqueKey] = Db::raw("concat(`{$uniqueKey}`,'_del_{$currTime}')");
                    }
                }
                $model->change($changeData, [['id', 'in', $delIds]]);
            }
            // 修改
            if ($updateItems = $data['updateItems'] ?? []) {
                $model->batchChange($updateItems);
            }
            // 新增
            if ($insertItems = $data['insertItems'] ?? []) {
                $model->addRows($insertItems);
            }
        }
        // 处理pid
        foreach ($parentSystemKeyData as $type => $parentSystemKeys) {
            /** @var RouteDataType $typeObj */
            $typeObj = RouteDataType::byValue($type);
            // 获取当前父system_key对应的id集合
            $parentSystemKeyIds = $typeObj->getParentModel()->getColumnOptions('id', [['system_key', 'in', array_unique($parentSystemKeys)]], [],[], 'system_key');
            // 获取要处理pid的数据集合
            $systemKeyDbDatas = [];
            foreach ($parentSystemKeys as $systemKey => $parentSystemKey) {
                $pid = $parentSystemKey && isset($parentSystemKeyIds[$parentSystemKey]) ? $parentSystemKeyIds[$parentSystemKey] : 0;
                $systemKeyDbDatas[] = [
                    'system_key' => $systemKey,
                    $typeObj->getTablePidKey() => $pid
                ];
            }
            $typeObj->getModel()->batchChange($systemKeyDbDatas, 'system_key');
        }
    }

    private static function handleDbData(array &$dbData) {
        $currDbDatas = self::getCurrDbDatas();
        $currTime = self::getHandleTime();
        foreach ($dbData as $type => $data) {
            $systemDbDatas = $currDbDatas[$type]['systemDbDatas'];
            $insertItems = [];
            $updateItems = [];
            foreach ($data['items'] as $dbItem) {
                ['system_key' => $systemKey] = $dbItem;
                if ($systemDbData = $systemDbDatas[$systemKey] ?? []) {
                    $updateItems[] = [
                            'id' => $systemDbData['id'],
                            'last_update_time' => $currTime,
                        ] + $dbItem;
                    unset($systemDbDatas[$systemKey]);
                } else {
                    $insertItems[] = [
                            'create_time' => $currTime,
                            'last_update_time' => $currTime,
                        ] + $dbItem;
                }
            }
            // 要删除的ids
            $delIds = array_column($systemDbDatas, 'id');
            // 组装数据
            $dbData[$type]['insertItems'] = $insertItems;
            $dbData[$type]['updateItems'] = $updateItems;
            $dbData[$type]['delIds'] = $delIds;
        }
    }


    /**
     * 处理树结构数据
     * @param array $dbData
     * @param array $parentSystemKeyData ['key'=>'parent_key']
     * @param string $parentSystemKey
     * @param array $configTreeRequests
     */
    private static function handleTreeItems(array &$dbData, array &$parentSystemKeyData, string $parentSystemKey, array $treeItems) {
        foreach ($treeItems as $treeItem) {
            ['type' => $type, 'system_key' => $systemKey] = $treeItem;
            /** @var RouteDataType $typeObj */
            $typeObj = RouteDataType::byValue($type);
            $getDbItemMethod = 'getDbItemBy' . ucfirst($type);
            $dbItem = self::$getDbItemMethod($treeItem);
            $dbItem['system_key'] = $systemKey;
            $dbItem['is_demo'] = $treeItem['is_demo'] ?? IsDemo::NO;
            if (!IsDemo::isValue($dbItem['is_demo'], true)) {
                self::throwArgs($typeObj->getTitle() . '[is_demo]错误！', $treeItem);
            }

            $dbData[$type]['items'] = $dbData[$type]['items'] ?? [];
            $dbData[$type]['items'][] = $dbItem;
            $parentSystemKeyData[$type] = $parentSystemKeyData[$type] ?? [];
            $parentSystemKeyData[$type][$systemKey] = $parentSystemKey;
            $treeItemSubItems = $treeItem['items'] ?? [];
            self::handleTreeItems($dbData, $parentSystemKeyData, $systemKey, $treeItemSubItems);
        }
    }

    // 获取分组item
    private static function getDbItemByGroup(array $args) {
        $item = [];

        $item['name'] = $args['name'] ?? '';
        if (!$item['name']) {
            self::throwArgs('分组名称[name]不能为空！', $args);
        }

        $item['namespace'] = $args['namespace'] ?? '';
        if (!is_string($item['namespace'])) {
            self::throwArgs('分组命名空间[namespace]需为字符串格式！', $args);
        }

        $item['enable_login_auth'] = $args['enable_login_auth'] ?? EnableLoginAuth::NIL;
        if (!EnableLoginAuth::isValue($item['enable_login_auth'])) {
            self::throwArgs('分组登录鉴权[enable_login_auth]配置错误！', $args);
        }

        $item['desc'] = $args['desc'] ?? '';
        if (!is_string($item['desc'])) {
            self::throwArgs('分组描述[desc]需为字符串格式！', $args);
        }

        return $item;
    }

    // 获取路由item
    private static function getDbItemByRoute(array $args) {
        $item = [
            'route' => $args['route'] ?? '',
        ];

        $item['name'] = $args['name'] ?? '';
        if (!$item['name']) {
            self::throwArgs('路由别名[name]不能为空！', $args);
        }

        $item['desc'] = $args['desc'] ?? '';
        if (!is_string($item['desc'])) {
            self::throwArgs('路由描述[desc]需为字符串格式！', $args);
        }

        $item['rule'] = $args['rule'] ?? '';
        if (!$item['rule']) {
            self::throwArgs('路由规则[rule]不能为空！', $args);
        }

        $item['enable_login_auth'] = $args['enable_login_auth'] ?? EnableLoginAuth::NIL;
        if (!EnableLoginAuth::isValue($item['enable_login_auth'])) {
            self::throwArgs('路由登录鉴权[enable_login_auth]配置错误！', $args);
        }
        
        return $item;
    }

    // 获取路由请求项item
    private static function getDbItemByRequest(array $args) {
        $item = [];

        $item['desc'] = $args['desc'] ?? '';
        if (!is_string($item['desc'])) {
            self::throwArgs('路由请求项描述[desc]需为字符串格式！', $args);
        }

        $item['method'] = $args['method'] ?? '';
        if ($item['method'] && !RequestMethod::isValue($item['method'])) {
            self::throwArgs('路由请求项请求类型[method]错误！', $args);
        }

        $item['require_params'] = $args['require_params'] ?? [];
        if (!is_array($item['require_params'])) {
            self::throwArgs('路由请求项必须参数[require_params]需为数组key-value格式！', $args);
        }
        $item['require_params'] = $item['require_params'] ? json_encode($item['require_params'], JSON_UNESCAPED_UNICODE) : '';

        $item['enable_login_auth'] = $args['enable_login_auth'] ?? EnableLoginAuth::NIL;
        if (!EnableLoginAuth::isValue($item['enable_login_auth'])) {
            self::throwArgs('路由请求项登录鉴权[enable_login_auth]配置错误！', $args);
        }

        return $item;
    }

    // 抛出异常
    private static function throwArgs(string $msg, array $args) {
        throw new \RuntimeException("下属配置：{$msg}\r\n" . json_encode($args, JSON_UNESCAPED_UNICODE));
    }


    /**
     * 获取当前数据库数据
     * @param false $isSystem
     * @return array
     */
    private static function getCurrDbDatas(): array {
        static $dbDatas;
        if (!isset($dbDatas)) {
            $dbDatas[RouteDataType::GROUP]['dbDatas'] = self::getAdminRouteGroupModel()->getOwnRowsToArray([['state', '<>', State::DELETED]]);
            $dbDatas[RouteDataType::ROUTE]['dbDatas'] = self::getAdminRouteModel()->getOwnRowsToArray([['state', '<>', State::DELETED]]);
            $dbDatas[RouteDataType::REQUEST]['dbDatas'] = self::getAdminRouteRequestModel()->getOwnRowsToArray([['state', '<>', State::DELETED]]);
        }
        foreach ($dbDatas as $type => $typeData) {
            $dbDatas[$type]['systemDbDatas'] = array_column(array_map(function ($data) {
                if ($data['system_key']) {
                    return $data;
                }
            }, $typeData['dbDatas']), null, 'system_key');
        }
        return $dbDatas;
    }

    // 获取处理时间
    private static function getHandleTime() {
        static $time;
        $time = $time ?? time();
        return $time;
    }

}